package com.evil.common.mongo.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import com.alibaba.fastjson.JSON;
import com.evil.common.core.exception.BusinessException;
import com.evil.common.core.util.StreamUtil;
import com.evil.common.mongo.entity.Name;
import com.evil.common.mongo.enums.MongoRCodeEnum;
import com.evil.common.mongo.service.Service;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ResourceLoader;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.transaction.annotation.Transactional;

import java.io.*;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.function.Consumer;
import java.util.stream.Collectors;

/**
 * @author liyang
 * @date 2024-06-14 09:39
 */
@Slf4j
@SuppressWarnings("unchecked")
public abstract class ServiceImpl<NAME extends Serializable, M extends MongoRepository<T, NAME>, T extends Name<NAME>> implements Service<NAME, T> {

    protected Class<T> entityClass = currentModelClass();
    @Autowired
    protected M repository;
    @Autowired
    private ResourceLoader resourceLoader;

    @Override
    public M getRepository() {
        return repository;
    }

    public abstract String name2Str(NAME name);

    @Override
    public String names2Str(Collection<NAME> names) {
        return names.stream().map(this::name2Str).collect(Collectors.joining(","));
    }

    public Class<T> getEntityClass() {
        return entityClass;
    }

    protected String getEntityName() {
        try {
            T t = this.entityClass.getDeclaredConstructor().newInstance();
            return t.getEntityName();
        } catch (ReflectiveOperationException e) {
            log.error("getEntityName|error|e:", e);
            throw new BusinessException(MongoRCodeEnum.RUN_ERROR);
        }
    }

    protected Class<T> currentModelClass() {
        Type type = getClass().getGenericSuperclass();
        if (type instanceof ParameterizedType parameterizedType) {
            int index = 1;
            if (parameterizedType.getRawType().equals(ServiceImpl.class)) {
                index = 2;
            }
            Type[] typeArguments = parameterizedType.getActualTypeArguments();
            return (Class<T>) typeArguments[index];
        }
        log.error("currentModelClass|error");
        throw new BusinessException(MongoRCodeEnum.RUN_ERROR);
    }

    @Override
    public String existMsg(NAME name) {
        return String.format("%s[%s]%s", this.getEntityName(), name, MongoRCodeEnum.ENTITY_EXIST.getMessage());
    }

    @Override
    public String existMsg(Collection<NAME> names) {
        if (CollectionUtil.isEmpty(names)) {
            return "";
        }
        return String.format("%s[%s]%s", this.getEntityName(), this.names2Str(names), MongoRCodeEnum.ENTITY_EXIST.getMessage());
    }

    @Override
    public String notExistMsg(NAME name) {
        return String.format("%s[%s]%s", this.getEntityName(), name, MongoRCodeEnum.ENTITY_NOT_EXIST.getMessage());
    }

    @Override
    public String notExistMsg(Collection<NAME> names) {
        return String.format("%s[%s]%s", this.getEntityName(), this.names2Str(names), MongoRCodeEnum.ENTITY_NOT_EXIST.getMessage());
    }

    @Override
    public void throwNotExist(NAME name) {
        throw new BusinessException(MongoRCodeEnum.ENTITY_EXIST, this.notExistMsg(name));
    }

    @Override
    public void throwNotExist(Collection<NAME> names) {
        throw new BusinessException(MongoRCodeEnum.ENTITY_NOT_EXIST, this.notExistMsg(names));
    }

    @Override
    public T add(T t) {
        if (repository.existsById(t.getName())) {
            String msg = this.existMsg(t.getName());
            log.error("add|error|msg:{}", msg);
            throw new BusinessException(MongoRCodeEnum.ENTITY_EXIST, msg);
        }
        return repository.insert(t);
    }

    @Override
    public boolean existsByName(NAME name) {
        return repository.existsById(name);
    }

    @Override
    public Optional<T> isExistByName(NAME name) {
        return repository.findById(name);
    }

    @Override
    public T findByName(NAME name) {
        return this.isExistByName(name).orElseThrow(() -> {
            String msg = this.notExistMsg(name);
            log.error("findByName|error|msg:{}", msg);
            return new BusinessException(MongoRCodeEnum.ENTITY_NOT_EXIST, msg);
        });
    }

    @Override
    public List<T> isExistByNames(Collection<NAME> names) {
        if (CollectionUtil.isEmpty(names)) {
            return new ArrayList<>();
        }
        return repository.findAllById(names);
    }

    @Override
    public List<T> findByNames(Collection<NAME> names) {
        List<T> ts = this.isExistByNames(names);
        if (ts.size() != names.size()) {
            List<NAME> tNames = StreamUtil.transListT(ts, T::getName);
            List<NAME> unExists = names.stream().filter(e -> !tNames.contains(e)).collect(Collectors.toList());
            throw new BusinessException(MongoRCodeEnum.ENTITY_NOT_EXIST, this.notExistMsg(unExists));
        }
        return ts;
    }

    @Override
    public Map<NAME, T> findMapByNames(Collection<NAME> names) {
        return StreamUtil.toMapK(this.findByNames(names), T::getName);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public T update(T t) {
        return this.update(t, null);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public T update(T t, Consumer<T> consumer) {
        return this.update(t, consumer, null);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public T update(T t, Consumer<T> consumer, Consumer<T> callback) {
        if (!this.existsByName(t.getName())) {
            throw new BusinessException(MongoRCodeEnum.ENTITY_NOT_EXIST, this.notExistMsg(t.getName()));
        }
        if (null != consumer) {
            consumer.accept(t);
        }
        T save = repository.save(t);
        if (null != callback) {
            callback.accept(save);
        }
        return save;
    }

    @Override
    public void del(NAME name) {
        repository.deleteById(name);
    }

    @Override
    public void init(String suffix) {
        List<T> ts = this.readJsonFile(suffix);
        repository.deleteAll();
        repository.insert(ts);
    }

    @Override
    public List<T> readJsonFile(String suffix) {
        Class<T> tClass = this.getEntityClass();
        String name = tClass.getSimpleName().toLowerCase();
        log.info("readJsonFile|name:{}", name);
        try {
            InputStream inputStream = resourceLoader.getResource(String.format("classpath:data/%s%s.json", name, StringUtils.defaultIfBlank(suffix, ""))).getInputStream();
            try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) {
                // 读取文件内容并解析为JSONObject
                String jsonStr = reader.lines().collect(Collectors.joining(System.lineSeparator()));
                return JSON.parseArray(jsonStr, tClass);
            }
        } catch (IOException e) {
            log.info("readJsonFile|name:{}|error:", name, e);
            throw new BusinessException(MongoRCodeEnum.ENTITY_INIT_ERROR);
        }
    }
}
